package com.xuecheng.media.service.jobhandler;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.media.mapper.MediaProcessHistoryMapper;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.model.po.MediaProcessHistory;
import com.xuecheng.media.service.MediaFilesService;
import com.xuecheng.media.service.MediaProcessService;
import com.xuecheng.utils.Mp4VideoUtil;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author 小班哥
 * @version 1.0
 * @date 2023/4/12 21:15
 * @description 视频转码
 */

@Component
@Slf4j
public class VideoTranscoding {

    @Autowired
    private MediaProcessService mediaProcessService;

    @Autowired
    private MediaFilesService mediaFilesService;

    @Autowired
    private MediaProcessHistoryMapper mediaProcessHistoryMapper;


    @Value("${videoprocess.ffmpegpath}")
    private String ffmpegPath;

    private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);

    @XxlJob("VideoDisposeTask")
    @Transactional
    public void VideoDisposeTask() throws Exception{
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();//当前分片序号
        int shardTotal = XxlJobHelper.getShardTotal();//总分片数

        //拿到CPU的核心数,用来开辟线程，为了避免CPU占用过高这里取出CPU的核数/2
        double cpu = Runtime.getRuntime().availableProcessors() / 2;
        int ceil = (int) Math.ceil(cpu);

        //获取任务
        List<MediaProcess> mediaProcessList = mediaProcessService.getMediaProcessList(shardTotal, shardIndex, ceil);
        int size = mediaProcessList.size();
        if (size <= 0){
            log.info("获取任务失败,任务数{}",size);
            return;
        }

        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(size);

        //创建一个线程计数器
        CountDownLatch countDownLatch = new CountDownLatch(size);


        //争抢任务，那个线程抢到谁去执行
        mediaProcessList.forEach( DataRow -> {
            try {
                //开启线程去执行任务
                threadPool.execute(() -> {
                    //开启开启乐观锁
                    boolean startTask = mediaProcessService.StartTask(DataRow.getId());
                    //判断是否抢到
                    if (!startTask) {
                        log.info("抢占任务失败,任务ID：{} 当前分片序号：{}", DataRow.getId(), shardIndex);
                        return;
                    }

                    //=====拿到需要处理文件的路径======
                    //从MinIO下载视频
                    File downloadMinIOFile = mediaFilesService.DownloadMinIOFile(DataRow.getBucket(), DataRow.getFilePath());
                    if (downloadMinIOFile == null) {
                        //下载文件失败、记录失败次数
                        log.info("下载文件失败,bucket：{},ObjectName{}", DataRow.getBucket(), DataRow.getFilePath());
                        Update_Process_Status(DataRow, "3", "下载文件失败,bucket{" + DataRow.getBucket() + "},ObjectName{" + DataRow.getFilePath() + "}");
                        downloadMinIOFile.delete();
                        return;
                    }

                    //抢到任务，执行转码
                    String absolutePath = downloadMinIOFile.getAbsolutePath();//下载文件的绝对路径
                    File tempFile = null;
                    try {
                        //创建一个临时文件来给转码文件提供一个路径
                        tempFile = File.createTempFile(DataRow.getFileId(), ".mp4");
                    } catch (IOException e) {
                        log.info("创建临时文件抛出异常,异常原因{}", e.getMessage());
                        Update_Process_Status(DataRow, "3", "创建临时文件抛出异常,异常原因:{" + e.getMessage() + "}");
                        //删除临时文件
                        downloadMinIOFile.delete();
                        tempFile.delete();
                        return;
                    }
                    //开始转码
                    String videoTask = ProcessingVideoTask(ffmpegPath, absolutePath, tempFile.getName(), tempFile.getAbsolutePath());
                    //如果成功会返回success，如果不是那么就说明失败了
                    if (!"success".equals(videoTask)) {
                        log.info("转码失败:任务ID{},失败原因{}", DataRow.getId(), videoTask);
                        Update_Process_Status(DataRow, "3", "转码失败,任务ID:{" + DataRow.getId() + "},失败原因:{" + videoTask + "}");
                        //删除临时文件
                        downloadMinIOFile.delete();
                        tempFile.delete();
                        return;
                    }

                    //==========如果能执行到这里说明转码已经成功了========
                    //将文件上传到MinIO
                    String tempFileName = tempFile.getName();
                    String substring = tempFileName.substring(tempFileName.lastIndexOf("."));
                    String ObjectName = MergeFileName(DataRow.getFileId(), substring);
                    //拿到文件的ContType
                    String contentType = mediaFilesService.GetFileContentType(substring);
                    boolean loadFileMinIo = mediaFilesService.LoadFileMinIo(DataRow.getBucket(), ObjectName, contentType, tempFile.getAbsolutePath());
                    //删除临时文件
                    downloadMinIOFile.delete();
                    tempFile.delete();
                    if (!loadFileMinIo) {
                        //上传失败MinIo失败
                        log.info("上传失败MinIo失败,bucket:{},ObjectName{}", DataRow.getBucket(), ObjectName);
                        Update_Process_Status(DataRow, "3", "{上传失败MinIo失败}");
                        return;
                    }

                    //上传到MinIO成功后更新数据库
                    //向任务历史表加入数据
                    MediaProcessHistory mediaProcessHistory = new MediaProcessHistory();
                    BeanUtils.copyProperties(DataRow, mediaProcessHistory);
                    mediaProcessHistory.setFinishDate(LocalDateTime.now());
                    mediaProcessHistory.setUrl("/" + DataRow.getBucket() + "/" + ObjectName);
                    mediaProcessHistory.setFilePath(tempFile.getAbsolutePath());
                    mediaProcessHistory.setStatus("2");
                    int insert = mediaProcessHistoryMapper.insert(mediaProcessHistory);
                    if (insert <= 0) {
                        //插入任务历史表失败
                        log.info("插入任务历史表失败");
                        Update_Process_Status(DataRow, "3", "{插入任务历史表失败}");
                        return;
                    }
                    //删除文件待处理表中的数据
                    boolean remove = mediaProcessService.removeById(DataRow);
                    //更新媒资信息的URL
                    LambdaQueryWrapper<MediaFiles> wrapper = new LambdaQueryWrapper<>();
                    wrapper.eq(MediaFiles::getId, DataRow.getFileId());
                    MediaFiles mediaFiles = new MediaFiles();
                    mediaFiles.setUrl("/" + DataRow.getBucket() + "/" + ObjectName);
                    boolean update = mediaFilesService.update(mediaFiles, wrapper);
                    if (!update) {
                        //插入任务历史表失败
                        log.info("插入任务历史表失败");
                        Update_Process_Status(DataRow, "3", "{插入任务历史表失败}");
                        return;
                    }
                });
            }catch (Exception e){
                Update_Process_Status(DataRow, "3", "{"+e.getMessage()+"}");
            }finally {
                //计数器-1
                countDownLatch.countDown();
            }
        });

        //阻塞,让代码在此等待下一轮执行，直到计数器等于0才放行,最大等待时间30分钟
        countDownLatch.await(30 , TimeUnit.MINUTES);
        log.debug("代码执行完毕!");
    }


    /**
     *  转换视频文件格式
     * @param ffmpeg_path ffmpeg.exe的路径
     * @param video_path  源avi视频的路径
     * @param mp4_name 转换后mp4文件的名称
     * @param mp4_path  转换后mp4文件的路径
     * @return
     */
    private String ProcessingVideoTask(String ffmpeg_path , String video_path , String mp4_name , String mp4_path){
        Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
        //开始视频转换，成功将返回success
        return videoUtil.generateMp4();
    }

    /**
     * 拿到合并文件的逻辑名称
     * @param FileMD5 文件MD5值
     * @param FileExtension 扩展名
     * @return
     */
    private String MergeFileName(String FileMD5 , String FileExtension){
        return FileMD5.substring(0 , 1) + "/" + FileMD5.substring(1 , 2) + "/" + FileMD5 + "/" + FileMD5 + FileExtension;
    }


    /**
     * 处理任务失败，释放锁
     * @param mediaProcess mediaProcess对象
     * @param Message  失败信息
     * @param Status 状态码
     * @return
     */
    private boolean Update_Process_Status(MediaProcess mediaProcess,String Status ,  String Message){
        mediaProcess.setFailCount(mediaProcess.getFailCount() + 1);
        mediaProcess.setStatus(Status);
        mediaProcess.setErrormsg(Message);
        boolean updateById = mediaProcessService.updateById(mediaProcess);
        return updateById;
    }

}
